/**
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 *   
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 */
package org.jiopi.ibean.kernel.context;

import java.io.File;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Properties;
import org.apache.log4j.Logger;
import org.jiopi.ibean.bootstrap.Closeable;
import org.jiopi.ibean.bootstrap.CloseableManager;
import org.jiopi.ibean.bootstrap.util.DirLoker;
import org.jiopi.ibean.kernel.IBeanCentralConsoleKernel;
import org.jiopi.ibean.kernel.KernelConstants;
import org.jiopi.ibean.kernel.NameVersion;
import org.jiopi.ibean.kernel.config.BlueprintAnnotations;
import org.jiopi.ibean.kernel.context.classloader.BlueprintClassLoaderManager;
import org.jiopi.ibean.kernel.context.classloader.CommonLibClassLoaderManager;
import org.jiopi.ibean.kernel.context.classloader.ContextRedefineableClassLoader;
import org.jiopi.ibean.kernel.context.classloader.IBeanClassLoader;
import org.jiopi.ibean.kernel.repository.ModuleResource;
import org.jiopi.ibean.kernel.repository.Repository;
import org.jiopi.ibean.kernel.util.ConfigUtil;
import org.jiopi.ibean.share.ShareConstants;
import org.jiopi.ibean.kernel.util.FileUtil;
import org.jiopi.ibean.share.ShareUtil.ResourceUtil;

import sun.misc.CompoundEnumeration;
/**
 * 
 * iBeanContext关联的CentralConsoleKernel
 * 保证不同的iBeanContext自成体系
 * 
 * 即 相同 NameVersion 的 ModuleImpl 在ContextCentralConsoleKernel 中只能存在一个实例
 * 
 * 
 * @since 2010.4.18
 *
 */
public class ContextCentralConsoleKernel{
	
	private static Logger logger = Logger.getLogger(ContextCentralConsoleKernel.class);
	
	private final Properties jiopiConfig;
	
	/**
	 * 当前CentralConsoleKernel的iBean contextClassLoader
	 */
	public final ClassLoader contextClassLoader;
	
	public final File contextDir;
	
	public final HashSet<NameVersion> contextBlueprints = new HashSet<NameVersion>();
	
	/**
	 * blueprint class loader manager for this context
	 */
	public final BlueprintClassLoaderManager blueprintClassLoadManager = new BlueprintClassLoaderManager(this);
	
	public final CommonLibClassLoaderManager commonLibClassLoaderManager = new CommonLibClassLoaderManager(this);
	
	/**
	 * 保存函数参数请求的NameVersion对应的ModuleConsole Map
	 */
	private HashMap<NameVersion,ModuleConsoleImpl> accessModuleConsoleMap = new HashMap<NameVersion,ModuleConsoleImpl>();
	
	/**
	 * Module的真实版本对应的 ModuleConsole Map
	 * 该缓存保证同一真正NameVersion的ModuleConsole的唯一
	 */
	private HashMap<NameVersion,ModuleConsoleImpl> realModuleConsoleMap = new HashMap<NameVersion,ModuleConsoleImpl>();
	
	/**
	 * 初始化iBean Context Central Console Kernel
	 * @param contextClassLoader
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public ContextCentralConsoleKernel(ClassLoader contextClassLoader){
		this.contextClassLoader = contextClassLoader;
		jiopiConfig = ConfigUtil.mergeProperties(ResourceUtil.getJIOPICascadingConfig(), true);
		String workDirPath = jiopiConfig.getProperty(ShareConstants.IBEAN_WORK_DIR,ShareConstants.IBEAN_WORK_DIR_DEFAULT);
		workDirPath = FileUtil.getPathWithSystemProperty(workDirPath);
		if(logger.isDebugEnabled()) logger.debug(contextClassLoader+" use work dir "+workDirPath);
		
		String contextDirPath = FileUtil.joinPath(workDirPath,KernelConstants.KERNEL_CONTEXT_DIR);
		try {
			contextDir = DirLoker.getUniqueDirLock(contextDirPath);
		} catch (IOException e) {
			logger.error("can't lock context dir "+contextDirPath, e);
			throw new RuntimeException(e);
		}
		if(logger.isDebugEnabled())logger.debug("lock context dir "+contextDir.getAbsolutePath());
		CloseableManager.register(new ContextClose());
		
		//init blueprint in context
		try {
			Enumeration<URL>[] contextBlueprintList = new Enumeration[2];
			contextBlueprintList[0] = contextClassLoader.getResources("blueprint.xml");
			contextBlueprintList[1] = contextClassLoader.getResources("blueprint.xml.jiopi");
			Enumeration<URL> contextBlueprints = new CompoundEnumeration(contextBlueprintList);
			while(contextBlueprints.hasMoreElements()){
				URL contextBlueprintURL = contextBlueprints.nextElement();
				File contextBlueprintConfigFile = FileUtil.getJarDirFileByResourceURL(contextBlueprintURL);
				
				if(contextBlueprintConfigFile.isFile()){
					if(logger.isDebugEnabled()){
						logger.debug("parse context blueprint "+contextBlueprintConfigFile);
					}
					ModuleResource.JarInfo ji = new ModuleResource.JarInfo(contextBlueprintConfigFile);
					if(ji.jarType!=-1){
						this.contextBlueprints.add(new NameVersion(ji.resourceName,ji.resourceVersion));
					}else{
						BlueprintAnnotations blueprint = BlueprintAnnotations.createBlueprintAnnotationsFromJar(contextBlueprintConfigFile);
						this.contextBlueprints.add(new NameVersion(blueprint.getName(),blueprint.getVersion()));
					}
					logger.info("load blueprint from context:"+contextBlueprintConfigFile.getAbsolutePath());
				}else{
					logger.warn("contextBlueprintConfigFile "+contextBlueprintConfigFile+" is not a local file");
				}
				
			}
		} catch (Exception e) {
			logger.error("unexpected error while parsing context blueprints",e);
		}
		
		//init Context Redefine
		Instrumentation INS = IBeanCentralConsoleKernel.getInstrumentation();
		if(INS!=null){
			ContextRedefineableClassLoader crcl = ContextRedefineableClassLoader.getContextRedefineableClassLoader(contextClassLoader, this);
			if(crcl!=null){
				try{
					Method method = Instrumentation.class.getMethod("addTransformer", new Class<?>[]{ClassFileTransformer.class,boolean.class});
					method.invoke(INS, new Object[]{crcl,true});
					crcl.redefineContext();
				}catch(Exception e){
					logger.warn("retransformClasses not support , Redefine class may not perfect work");
					INS.addTransformer(crcl);
				}
			}
		}
	}
	
	/**
	 * 获取ModuleConsole对象
	 * @param moduleName   模块名称
	 * @param compatibleVersion   版本
	 * @return
	 */
	public ModuleConsoleImpl accessModuleConsole(String moduleName,String compatibleVersion) {
		if(moduleName==null) return null;
		NameVersion needNameVersion = new NameVersion(moduleName,compatibleVersion);
		
		ModuleConsoleImpl mc = null;
		
		synchronized(accessModuleConsoleMap){
			mc = accessModuleConsoleMap.get(needNameVersion);
		}
		if(mc == null){//需要初始化
			synchronized(("mc"+needNameVersion.id).intern()){
				synchronized(accessModuleConsoleMap){
					mc = accessModuleConsoleMap.get(needNameVersion);
				}
				if(mc==null){
					//从资源库使用NameVersion获取
					ModuleResource mr = Repository.getModuleResource(needNameVersion,Repository.MODULE);
					if(mr == null) throw new RuntimeException("Module of "+moduleName+(compatibleVersion==null?"":compatibleVersion+" ")+" is not found");
					//System.out.println("get ModuleResource "+mr);
					synchronized(realModuleConsoleMap){
						mc = realModuleConsoleMap.get(mr);
					}
					if(mc == null){//未初始化
						synchronized(("mr"+mr.id).intern()){
							synchronized(realModuleConsoleMap){
								mc = realModuleConsoleMap.get(mr);
							}
							if(mc==null){
								//真正的初始化
								String moduleTempDir = FileUtil.joinPath(this.contextDir.getAbsolutePath(),KernelConstants.TMP_DIR,mr.id);
								String moduleDir = FileUtil.joinPath(this.contextDir.getAbsolutePath(),mr.id);
								NameVersion moduleNameVersion = new NameVersion(moduleName,mr.version);
								IBeanClassLoader moduleConsoleClassLoader = new IBeanClassLoader(moduleNameVersion,mr,this,moduleDir,moduleTempDir);
								mc = new ModuleConsoleImpl(moduleNameVersion,moduleConsoleClassLoader,this);
								synchronized(realModuleConsoleMap){
									//System.out.println("put ModuleConsoleImpl "+mr);
									realModuleConsoleMap.put(mr, mc);
								}
								synchronized(accessModuleConsoleMap){
									accessModuleConsoleMap.put(needNameVersion,mc);
								}
							}
						}
					}
				}
			}
		}
		//System.out.println(this+"???get "+needNameVersion+" using "+mc.getClassLoader());
		return mc;
	}
	
	public ClassLoader getClassLoader() {
		return null;
	}
	
	private class ContextClose implements Closeable{
		public void close(){
			//删除临时目录
			if(contextDir.isDirectory()){
				File tmpDir = new File(contextDir,KernelConstants.TMP_DIR);
				FileUtil.deleteFile(tmpDir);
				logger.info("tmpDir "+tmpDir+" deleted.");
			}
		}
	}
}
